1   /*
2    * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions
6    * are met:
7    *
8    *   - Redistributions of source code must retain the above copyright
9    *     notice, this list of conditions and the following disclaimer.
10   *
11   *   - Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in the
13   *     documentation and/or other materials provided with the distribution.
14   *
15   *   - Neither the name of Oracle nor the names of its
16   *     contributors may be used to endorse or promote products derived
17   *     from this software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  
33  import java.applet.Applet;
34  import java.awt.Image;
35  import java.awt.Graphics;
36  import java.awt.Dimension;
37  import java.awt.event.MouseEvent;
38  import java.awt.event.MouseListener;
39  import java.awt.event.MouseMotionListener;
40  import java.net.URL;
41  import java.awt.image.IndexColorModel;
42  import java.awt.image.MemoryImageSource;
43  import java.io.BufferedReader;
44  import java.io.IOException;
45  import java.io.InputStream;
46  import java.io.InputStreamReader;
47  import java.io.StreamTokenizer;
48  import java.util.HashMap;
49  import java.util.Map;
50  import java.util.logging.Level;
51  import java.util.logging.Logger;
52  
53  
54  /*
55   * A set of classes to parse, represent and display Chemical compounds in
56   * .xyz format (see http://chem.leeds.ac.uk/Project/MIME.html)
57   */
58  /** The representation of a Chemical .xyz model */
59  final class XYZChemModel {
60  
61      float vert[];
62      Atom atoms[];
63      int tvert[];
64      int ZsortMap[];
65      int nvert, maxvert;
66      static final Map<String, Atom> atomTable = new HashMap<String, Atom>();
67      static Atom defaultAtom;
68  
69      static {
70          atomTable.put("c", new Atom(0, 0, 0));
71          atomTable.put("h", new Atom(210, 210, 210));
72          atomTable.put("n", new Atom(0, 0, 255));
73          atomTable.put("o", new Atom(255, 0, 0));
74          atomTable.put("p", new Atom(255, 0, 255));
75          atomTable.put("s", new Atom(255, 255, 0));
76          atomTable.put("hn", new Atom(150, 255, 150)); /* !!*/
77          defaultAtom = new Atom(255, 100, 200);
78      }
79      boolean transformed;
80      Matrix3D mat;
81      float xmin, xmax, ymin, ymax, zmin, zmax;
82  
83      XYZChemModel() {
84          mat = new Matrix3D();
85          mat.xrot(20);
86          mat.yrot(30);
87      }
88  
89      /** Create a Chemical model by parsing an input stream */
90      XYZChemModel(InputStream is) throws Exception {
91          this();
92          StreamTokenizer st = new StreamTokenizer(
93                  new BufferedReader(new InputStreamReader(is, "UTF-8")));
94          st.eolIsSignificant(true);
95          st.commentChar('#');
96  
97          try {
98              scan:
99              while (true) {
100                 switch (st.nextToken()) {
101                     case StreamTokenizer.TT_EOF:
102                         break scan;
103                     default:
104                         break;
105                     case StreamTokenizer.TT_WORD:
106                         String name = st.sval;
107                         double x = 0,
108                          y = 0,
109                          z = 0;
110                         if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
111                             x = st.nval;
112                             if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
113                                 y = st.nval;
114                                 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
115                                     z = st.nval;
116                                 }
117                             }
118                         }
119                         addVert(name, (float) x, (float) y, (float) z);
120                         while (st.ttype != StreamTokenizer.TT_EOL
121                                 && st.ttype != StreamTokenizer.TT_EOF) {
122                             st.nextToken();
123                         }
124 
125                 }   // end Switch
126 
127             }  // end while
128 
129             is.close();
130 
131         } // end Try
132         catch (IOException e) {
133         }
134 
135         if (st.ttype != StreamTokenizer.TT_EOF) {
136             throw new Exception(st.toString());
137         }
138 
139     }  // end XYZChemModel()
140 
141     /** Add a vertex to this model */
142     int addVert(String name, float x, float y, float z) {
143         int i = nvert;
144         if (i >= maxvert) {
145             if (vert == null) {
146                 maxvert = 100;
147                 vert = new float[maxvert * 3];
148                 atoms = new Atom[maxvert];
149             } else {
150                 maxvert *= 2;
151                 float nv[] = new float[maxvert * 3];
152                 System.arraycopy(vert, 0, nv, 0, vert.length);
153                 vert = nv;
154                 Atom na[] = new Atom[maxvert];
155                 System.arraycopy(atoms, 0, na, 0, atoms.length);
156                 atoms = na;
157             }
158         }
159         Atom a = atomTable.get(name.toLowerCase());
160         if (a == null) {
161             a = defaultAtom;
162         }
163         atoms[i] = a;
164         i *= 3;
165         vert[i] = x;
166         vert[i + 1] = y;
167         vert[i + 2] = z;
168         return nvert++;
169     }
170 
171     /** Transform all the points in this model */
172     void transform() {
173         if (transformed || nvert <= 0) {
174             return;
175         }
176         if (tvert == null || tvert.length < nvert * 3) {
177             tvert = new int[nvert * 3];
178         }
179         mat.transform(vert, tvert, nvert);
180         transformed = true;
181     }
182 
183     /** Paint this model to a graphics context.  It uses the matrix associated
184     with this model to map from model space to screen space.
185     The next version of the browser should have double buffering,
186     which will make this *much* nicer */
187     void paint(Graphics g) {
188         if (vert == null || nvert <= 0) {
189             return;
190         }
191         transform();
192         int v[] = tvert;
193         int zs[] = ZsortMap;
194         if (zs == null) {
195             ZsortMap = zs = new int[nvert];
196             for (int i = nvert; --i >= 0;) {
197                 zs[i] = i * 3;
198             }
199         }
200 
201         /*
202          * I use a bubble sort since from one iteration to the next, the sort
203          * order is pretty stable, so I just use what I had last time as a
204          * "guess" of the sorted order.  With luck, this reduces O(N log N)
205          * to O(N)
206          */
207 
208         for (int i = nvert - 1; --i >= 0;) {
209             boolean flipped = false;
210             for (int j = 0; j <= i; j++) {
211                 int a = zs[j];
212                 int b = zs[j + 1];
213                 if (v[a + 2] > v[b + 2]) {
214                     zs[j + 1] = a;
215                     zs[j] = b;
216                     flipped = true;
217                 }
218             }
219             if (!flipped) {
220                 break;
221             }
222         }
223 
224         int lim = nvert;
225         if (lim <= 0 || nvert <= 0) {
226             return;
227         }
228         for (int i = 0; i < lim; i++) {
229             int j = zs[i];
230             int grey = v[j + 2];
231             if (grey < 0) {
232                 grey = 0;
233             }
234             if (grey > 15) {
235                 grey = 15;
236             }
237             // g.drawString(names[i], v[j], v[j+1]);
238             atoms[j / 3].paint(g, v[j], v[j + 1], grey);
239             // g.drawImage(iBall, v[j] - (iBall.width >> 1), v[j + 1] -
240             // (iBall.height >> 1));
241         }
242     }
243 
244     /** Find the bounding box of this model */
245     void findBB() {
246         if (nvert <= 0) {
247             return;
248         }
249         float v[] = vert;
250         float _xmin = v[0], _xmax = _xmin;
251         float _ymin = v[1], _ymax = _ymin;
252         float _zmin = v[2], _zmax = _zmin;
253         for (int i = nvert * 3; (i -= 3) > 0;) {
254             float x = v[i];
255             if (x < _xmin) {
256                 _xmin = x;
257             }
258             if (x > _xmax) {
259                 _xmax = x;
260             }
261             float y = v[i + 1];
262             if (y < _ymin) {
263                 _ymin = y;
264             }
265             if (y > _ymax) {
266                 _ymax = y;
267             }
268             float z = v[i + 2];
269             if (z < _zmin) {
270                 _zmin = z;
271             }
272             if (z > _zmax) {
273                 _zmax = z;
274             }
275         }
276         this.xmax = _xmax;
277         this.xmin = _xmin;
278         this.ymax = _ymax;
279         this.ymin = _ymin;
280         this.zmax = _zmax;
281         this.zmin = _zmin;
282     }
283 }
284 
285 
286 /** An applet to put a Chemical model into a page */
287 @SuppressWarnings("serial")
288 public class XYZApp extends Applet implements Runnable, MouseListener,
289         MouseMotionListener {
290 
291     XYZChemModel md;
292     boolean painted = true;
293     float xfac;
294     int prevx, prevy;
295     float scalefudge = 1;
296     Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
297     String mdname = null;
298     String message = null;
299     Image backBuffer;
300     Graphics backGC;
301     Dimension backSize;
302 
303     private synchronized void newBackBuffer() {
304         backBuffer = createImage(getSize().width, getSize().height);
305         if (backGC != null) {
306             backGC.dispose();
307         }
308         backGC = backBuffer.getGraphics();
309         backSize = getSize();
310     }
311 
312     @Override
313     public void init() {
314         mdname = getParameter("model");
315         try {
316             scalefudge = Float.valueOf(getParameter("scale")).floatValue();
317         } catch (Exception ignored) {
318         }
319         amat.yrot(20);
320         amat.xrot(20);
321         if (mdname == null) {
322             mdname = "model.obj";
323         }
324         resize(getSize().width <= 20 ? 400 : getSize().width,
325                 getSize().height <= 20 ? 400 : getSize().height);
326         newBackBuffer();
327         addMouseListener(this);
328         addMouseMotionListener(this);
329     }
330 
331     @Override
332     public void destroy() {
333         removeMouseListener(this);
334         removeMouseMotionListener(this);
335     }
336 
337     @Override
338     public void run() {
339         InputStream is = null;
340         try {
341             Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
342             is = new URL(getDocumentBase(), mdname).openStream();
343             XYZChemModel m = new XYZChemModel(is);
344             Atom.setApplet(this);
345             md = m;
346             m.findBB();
347             float xw = m.xmax - m.xmin;
348             float yw = m.ymax - m.ymin;
349             float zw = m.zmax - m.zmin;
350             if (yw > xw) {
351                 xw = yw;
352             }
353             if (zw > xw) {
354                 xw = zw;
355             }
356             float f1 = getSize().width / xw;
357             float f2 = getSize().height / xw;
358             xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
359         } catch (Exception e) {
360             Logger.getLogger(XYZApp.class.getName()).log(Level.SEVERE, null, e);
361             md = null;
362             message = e.toString();
363         }
364         try {
365             if (is != null) {
366                 is.close();
367             }
368         } catch (Exception ignored) {
369         }
370         repaint();
371     }
372 
373     @Override
374     public void start() {
375         if (md == null && message == null) {
376             new Thread(this).start();
377         }
378     }
379 
380     @Override
381     public void stop() {
382     }
383     /* event handling */
384 
385     @Override
386     public void mouseClicked(MouseEvent e) {
387     }
388 
389     @Override
390     public void mousePressed(MouseEvent e) {
391         prevx = e.getX();
392         prevy = e.getY();
393         e.consume();
394     }
395 
396     @Override
397     public void mouseReleased(MouseEvent e) {
398     }
399 
400     @Override
401     public void mouseEntered(MouseEvent e) {
402     }
403 
404     @Override
405     public void mouseExited(MouseEvent e) {
406     }
407 
408     @Override
409     public void mouseDragged(MouseEvent e) {
410         int x = e.getX();
411         int y = e.getY();
412         tmat.unit();
413         float xtheta = (prevy - y) * (360.0f / getSize().width);
414         float ytheta = (x - prevx) * (360.0f / getSize().height);
415         tmat.xrot(xtheta);
416         tmat.yrot(ytheta);
417         amat.mult(tmat);
418         if (painted) {
419             painted = false;
420             repaint();
421         }
422         prevx = x;
423         prevy = y;
424         e.consume();
425     }
426 
427     @Override
428     public void mouseMoved(MouseEvent e) {
429     }
430 
431     @Override
432     public void update(Graphics g) {
433         if (backBuffer == null) {
434             g.clearRect(0, 0, getSize().width, getSize().height);
435         }
436         paint(g);
437     }
438 
439     @Override
440     public void paint(Graphics g) {
441         if (md != null) {
442             md.mat.unit();
443             md.mat.translate(-(md.xmin + md.xmax) / 2,
444                     -(md.ymin + md.ymax) / 2,
445                     -(md.zmin + md.zmax) / 2);
446             md.mat.mult(amat);
447             // md.mat.scale(xfac, -xfac, 8 * xfac / getSize().width);
448             md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
449             md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
450             md.transformed = false;
451             if (backBuffer != null) {
452                 if (!backSize.equals(getSize())) {
453                     newBackBuffer();
454                 }
455                 backGC.setColor(getBackground());
456                 backGC.fillRect(0, 0, getSize().width, getSize().height);
457                 md.paint(backGC);
458                 g.drawImage(backBuffer, 0, 0, this);
459             } else {
460                 md.paint(g);
461             }
462             setPainted();
463         } else if (message != null) {
464             g.drawString("Error in model:", 3, 20);
465             g.drawString(message, 10, 40);
466         }
467     }
468 
469     private synchronized void setPainted() {
470         painted = true;
471         notifyAll();
472     }
473 
474     @Override
475     public String getAppletInfo() {
476         return "Title: XYZApp \nAuthor: James Gosling \nAn applet to put"
477                 + " a Chemical model into a page.";
478     }
479 
480     @Override
481     public String[][] getParameterInfo() {
482         String[][] info = {
483             { "model", "path string", "The path to the model to be displayed"
484                 + " in .xyz format "
485                 + "(see http://chem.leeds.ac.uk/Project/MIME.html)."
486                 + "  Default is model.obj." },
487             { "scale", "float", "Scale factor.  Default is 1 (i.e. no scale)." }
488         };
489         return info;
490     }
491 }   // end class XYZApp
492 
493 
494 class Atom {
495 
496     private static Applet applet;
497     private static byte[] data;
498     private final static int R = 40;
499     private final static int hx = 15;
500     private final static int hy = 15;
501     private final static int bgGrey = 192;
502     private final static int nBalls = 16;
503     private static int maxr;
504     private int Rl;
505     private int Gl;
506     private int Bl;
507     private Image balls[];
508 
509     static {
510         data = new byte[R * 2 * R * 2];
511         int mr = 0;
512         for (int Y = 2 * R; --Y >= 0;) {
513             int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5);
514             int p = Y * (R * 2) + R - x0;
515             for (int X = -x0; X < x0; X++) {
516                 int x = X + hx;
517                 int y = Y - R + hy;
518                 int r = (int) (Math.sqrt(x * x + y * y) + 0.5);
519                 if (r > mr) {
520                     mr = r;
521                 }
522                 data[p++] = r <= 0 ? 1 : (byte) r;
523             }
524         }
525         maxr = mr;
526     }
527 
528     static void setApplet(Applet app) {
529         applet = app;
530     }
531 
532     Atom(int Rl, int Gl, int Bl) {
533         this.Rl = Rl;
534         this.Gl = Gl;
535         this.Bl = Bl;
536     }
537 
538     private int blend(int fg, int bg, float fgfactor) {
539         return (int) (bg + (fg - bg) * fgfactor);
540     }
541 
542     private void Setup() {
543         balls = new Image[nBalls];
544         byte red[] = new byte[256];
545         red[0] = (byte) bgGrey;
546         byte green[] = new byte[256];
547         green[0] = (byte) bgGrey;
548         byte blue[] = new byte[256];
549         blue[0] = (byte) bgGrey;
550         for (int r = 0; r < nBalls; r++) {
551             float b = (float) (r + 1) / nBalls;
552             for (int i = maxr; i >= 1; --i) {
553                 float d = (float) i / maxr;
554                 red[i] = (byte) blend(blend(Rl, 255, d), bgGrey, b);
555                 green[i] = (byte) blend(blend(Gl, 255, d), bgGrey, b);
556                 blue[i] = (byte) blend(blend(Bl, 255, d), bgGrey, b);
557             }
558             IndexColorModel model = new IndexColorModel(8, maxr + 1,
559                     red, green, blue, 0);
560             balls[r] = applet.createImage(
561                     new MemoryImageSource(R * 2, R * 2, model, data, 0, R * 2));
562         }
563     }
564 
565     void paint(Graphics gc, int x, int y, int r) {
566         Image ba[] = balls;
567         if (ba == null) {
568             Setup();
569             ba = balls;
570         }
571         Image i = ba[r];
572         int size = 10 + r;
573         gc.drawImage(i, x - (size >> 1), y - (size >> 1), size, size, applet);
574     }
575 }